/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Canadian Weather.
 *
 * The Initial Developer of the Original Code is
 *   George Bradt.
 *
 * Portions created by the Initial Developer are Copyright (C) 2011
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   George Bradt.
 *
 * Credits:
 *   Sun icons from Icons-Land at http://www.icons-land.com/vista-weather-icons.php
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var CanWeatherWidget = {
    imgSun:        '',
    imgMoon:       '',
    imgRainDrop:   '',
    imgSnowFlake:  '',
    imgCloud:      '',
    imgWind:       '',
    imgLightning:  '',
    imgFog:        '',
    imgDrizzle:    '',
    imgHaze:       '',
    imgHail:       '',
    imgFreezing:   '',
    imgDust:       '',
    imgSmoke:      '',
    imgCrystal:    '',
    imgH2O:        '',
    imgIncreasing: '',
    imgDecreasing: '',
    imgRecent:     '',
    imgTornado:    '',
    imgFunnel:     '',
    imgRadar:      '',
    imgFlag:       '',
    
    mouseData: [],
    radarEnabled: false,
    
    canvasNodeList: document.getElementsByTagName('canvas'),
    hiddenCanvasIndex: 1,
    imagesToDraw: []
};

// Trigger a build of the toolbar on browser startup, and when new browser windows open.
self.port.emit('newWindow');

/**
 * Build toolbar. Use double-buffering technique.
 * 
 * @param   {object} aOptions          All options.
 * @param   {object} aCombinedData     The most recently retrieved weather data for chosen site.
 * 
 * @returns {number} widgetWidth       Emitted to add-on code to set widget width.
 */
self.port.on('buildToolbar', function(aOptions, aCombinedData) {
    CanWeatherWidget.mouseData = [];
    CanWeatherWidget.imagesToDraw = [];
    
    if ( aOptions.location.siteCode ) {
        // Hide the add-on icon, which only will appear when installing.
        document.getElementById('canw-img').hidden = true;
    }
    else {
        document.getElementById('canw-img').hidden = false;
        CanWeatherWidget.radarEnabled = false;
        emitWidth(32);
        return;        
    }
    
    // Create a working canvas and give it an arbitrarily large width.
    // Prior to Australis, I could draw directly to hidden display canvas.
    var canvas = document.createElement('canvas');
    canvas.height = 16;
    canvas.width = 800;
    var ctx = canvas.getContext('2d');
    
    // Track the width of our content.
    // Use it to set the widget width and display canvas width.
    var widgetWidth = 0;
    
    if ( !aCombinedData ||
       ( !aOptions.toolbarDisplay.showConditionIcon &&
         !aOptions.radarEnabled &&
         !aOptions.forecastInToolbarEnabled &&
         !aOptions.toolbarDisplayEnabled ) ) {
        // Browser startup, or user has just turned off all toolbar display options.
        // Draw flag.
        CanWeatherWidget.radarEnabled = false;
        saveImg(CanWeatherWidget.imgFlag, 25);
        setDisplayCanvas(canvas, 25);
        flipVisibility();
        emitWidth(25);
        return;
    }
    
    // Start with some padding on the left.
    var drawStartPosX = 8;
    widgetWidth += drawStartPosX;
    
    // Set color, background color for the widget content.
    setColor();
        
    if ( aOptions.radarEnabled ) {
        CanWeatherWidget.radarEnabled = true;
        
        let tooltip = 'Station: ' + aOptions.radar.stationName;
        CanWeatherWidget.mouseData.push({posX: 0, tooltip: tooltip});
        
        saveImg(CanWeatherWidget.imgRadar, 18);
    }
    else {
        CanWeatherWidget.radarEnabled = false;
    }
    
    // Current condition icon.
    if ( aOptions.toolbarDisplay.showConditionIcon ) {
        if ( aOptions.radarEnabled ) {
            // Draw a vertical separator between condition and radar images.
            widgetWidth += 8;
            drawSeparator();
            setConditionTooltip(widgetWidth);
            widgetWidth += 8;
        }
        else {
            setConditionTooltip(0);
        }
        
        drawCondition(aCombinedData.currentConditions.iconCode,
                      aCombinedData.currentConditions.condition.toLowerCase(),
                      'current');
    }
    
    // Current condition text.
    if ( aOptions.toolbarDisplayEnabled ) {
        if ( !aOptions.toolbarDisplay.showConditionIcon ) {
            if ( aOptions.radarEnabled ) {
                // Draw a vertical separator between condition and radar images.
                widgetWidth += 8;
                drawSeparator();
                setConditionTooltip(widgetWidth);
                widgetWidth += 8;
            }
            else {
                setConditionTooltip(0);
            }
        }
        else {
            widgetWidth += 4;    
        }
        
        drawText(aCombinedData.barDisplayString + ' ');
    }
    
    // Forecast in toolbar.
    if ( aOptions.forecastInToolbarEnabled ) {
        var forecastIndices = getForecastIndices();
        
        if ( aOptions.forecastInToolbar.showToday &&
             forecastIndices[0] != -1 ) {
            // Today.
            initForecast(0);
            drawForecast(aCombinedData.forecasts.forecast[forecastIndices[0]]);
        }
        
        if ( aOptions.forecastInToolbar.showTonight &&
             forecastIndices[1] != -1 ) {
            // Tonight.
            initForecast(1);
            drawForecast(aCombinedData.forecasts.forecast[forecastIndices[1]]);
        }
        
        if ( aOptions.forecastInToolbar.showTodayPlus1 &&
             forecastIndices[2] != -1 ) {
            // Today + 1.
            initForecast(2);
            drawForecast(aCombinedData.forecasts.forecast[forecastIndices[2]]);
        }
        
        if ( aOptions.forecastInToolbar.showTodayPlus2 &&
             forecastIndices[3] != -1 ) {
            initForecast(3);
            drawForecast(aCombinedData.forecasts.forecast[forecastIndices[3]]);
        }
    }
    
    // Some padding on the right.
    widgetWidth += 8;
    
    setDisplayCanvas(canvas, widgetWidth);
    flipVisibility();
    emitWidth(widgetWidth);
    return;
    
    /**
     * Set tooltip text for current condition.
     * 
     * @param   {number} aPosition      PosX for start of this tooltip.
     */
    function setConditionTooltip(aPosition) {
        var tooltip = aCombinedData.currentConditions.condition + ' - ' +
                      'Observed: ' + aCombinedData.currentConditions.date;
        CanWeatherWidget.mouseData.push({posX: aPosition, tooltip: tooltip});
    }
    
    /**
     * Save info for image to be drawn later to canvas.
     * 
     * @param   {string} aImg      Data uri for img.
     * @param   {number} aWidth    Width of img.
     */
    function saveImg(aImg, aWidth) {
        // Prior to Australis, was able to draw immediately.
        // Cannot draw immediately to the dynamic canvas, since when
        // cloning to the display canvas, images probably will not be loaded yet.
        CanWeatherWidget.imagesToDraw.push([aImg, widgetWidth]);
        widgetWidth += aWidth;
    }
    
    /**
     * Draw a vertical separator.
     */
    function drawSeparator() {
        ctx.strokeStyle = 'Grey';
        ctx.lineWidth = 2;  
        ctx.beginPath();  
        ctx.moveTo(widgetWidth, 0);  
        ctx.lineTo(widgetWidth, canvas.height);  
        ctx.stroke();
    }
    
    /**
     * Draw text to canvas.
     * 
     * @param   {string} aText    Text to display.
     */
    function drawText(aText) {
        var fontStyle = ( aOptions.toolbarStyle.italic ) ? 'italic' : '';
        var fontWeight = ( aOptions.toolbarStyle.bold ) ? 'bold' : '';
        var fontSize;
        
        switch ( aOptions.toolbarStyle.fontsizeIndex ) {
            case 0:
                fontSize = '12';
                break;

            case 1:
                fontSize = '14';
                break;

            case 2:
                fontSize = '16';
                break;

            default:
                fontSize = '12';
        }

        ctx.font = fontStyle + ' ' + fontWeight + ' ' + fontSize +
                'px Segoe UI, Tahoma, Lucida Grande, sans-serif'; 
        ctx.textBaseline = 'bottom'; 
        ctx.fillText(aText, widgetWidth, canvas.height);
        
        widgetWidth += ctx.measureText(aText).width;
    }
    
    /**
     * Set color, background color for widget content.
     */
    function setColor() {
        var activeEventIndex = 0;
        var color = '';
        var backgroundColor = '';
    
        if ( aOptions.toolbarColorEnabled && aCombinedData.warnings.length > 0 ) {
            // Set colors based on most recent, and active, event. There may not be an active event.
            if ( aCombinedData.warnings[1].type == 'Ended' ) {
                // Perhaps another event that hasn't ended yet.
                for ( let i = 2; i < aCombinedData.warnings.length; i++ ) {
                    if ( aCombinedData.warnings[1].description != aCombinedData.warnings[i].description &&
                         aCombinedData.warnings[i].type != 'Ended' ) {
                        activeEventIndex = i;
                    }
                }
            }
            else {
                activeEventIndex = 1;
            }
        }
    
        if ( activeEventIndex ) {
            switch ( aCombinedData.warnings[activeEventIndex].priority ) {
                case 'Low':
                    if ( aOptions.toolbarStyle.color.priorityLow[0] ) {
                        color = aOptions.toolbarStyle.color.priorityLow[0];
                    }
    
                    if ( aOptions.toolbarStyle.color.priorityLow[1] ) {
                        backgroundColor = aOptions.toolbarStyle.color.priorityLow[1];
                    }
    
                    break;
    
                case 'Medium':
                    if ( aOptions.toolbarStyle.color.priorityMedium[0] ) {
                        color = aOptions.toolbarStyle.color.priorityMedium[0];
                    }
    
                    if ( aOptions.toolbarStyle.color.priorityMedium[1] ) {
                        backgroundColor = aOptions.toolbarStyle.color.priorityMedium[1];
                    }
    
                    break;
    
                case 'High':
                    if ( aOptions.toolbarStyle.color.priorityHigh[0] ) {
                        color = aOptions.toolbarStyle.color.priorityHigh[0];
                    }
    
                    if ( aOptions.toolbarStyle.color.priorityHigh[1] ) {
                         backgroundColor = aOptions.toolbarStyle.color.priorityHigh[1];
                    }
    
                    break;
    
                case 'Urgent':
                    if ( aOptions.toolbarStyle.color.priorityUrgent[0] ) {
                        color = aOptions.toolbarStyle.color.priorityUrgent[0];
                    }
    
                    if ( aOptions.toolbarStyle.color.priorityUrgent[1] ) {
                        backgroundColor = aOptions.toolbarStyle.color.priorityUrgent[1];
                    }
    
                    break;
    
                default:
                    self.port.emit('unexpectedPriorityError', aCombinedData.warnings[activeEventIndex].priority);
                    break;
            }
        }
        else {
            if ( aOptions.toolbarStyle.color.normal[0] ) {
                color = aOptions.toolbarStyle.color.normal[0];
            }
    
            if ( aOptions.toolbarStyle.color.normal[1] ) {
                backgroundColor = aOptions.toolbarStyle.color.normal[1];
            }
        }
        
        if ( backgroundColor ) {
            ctx.fillStyle = backgroundColor;
            ctx.fillRect (0, 0, canvas.width - widgetWidth, canvas.height);
        }
        
        ctx.fillStyle = ( color ) ? color : 'Black';
    }
    
    /**
     * Find and return indices of relevant forecasts.
     * 
     * @returns {object}    Array of forecast indices.
     */
    function getForecastIndices() {
        var forecastArray = aCombinedData.forecasts.forecast;
        var todayIndex = -1;
        var tonightIndex = -1;
        var todayPlus1Index = -1;
        var todayPlus2Index = -1;
        var todayPlus1Period = '';
        
        for ( let i = 0; i < forecastArray.length; i++ ) {
            if ( forecastArray[i].periodName == 'Today' ) {
                todayIndex = i;
            }
            else if ( forecastArray[i].periodName == 'Tonight' ) {
                // Use tonight low to also set tomorrow low.
                // This will be overwritten if tomorrow night exists.
                forecastArray[i + 1].low = forecastArray[i].low;
                tonightIndex = i;
            }
            else if ( todayPlus1Period == '' &&
                      forecastArray[i].periodName.split(/\s+/).length == 1 ) {
                // Tomorrow.
                todayPlus1Index = i;
                todayPlus1Period = forecastArray[i].periodName;
            }
            else if ( forecastArray[i].periodName.indexOf(todayPlus1Period) >= -1 &&
                      forecastArray[i].periodName.split(/\s+/).length > 1 &&
                      i > 0 ) {
                // Tomorrow night. Only exists for the final (5:00 PM) forecast of the day.
                // Get low and plug it into tomorrow, todayPlus2 forecasts.
                forecastArray[i - 1].low = forecastArray[i].low;
                forecastArray[i + 1].low = forecastArray[i].low;
            }
            else if ( forecastArray[i].periodName.indexOf(todayPlus1Period) == -1 &&
                      forecastArray[i].periodName.split(/\s+/).length == 1 ) {
                todayPlus2Index = i;
                break;
            }
        }
        
        return [todayIndex, tonightIndex, todayPlus1Index, todayPlus2Index];
    }
    
    /**
     * Draw vertical separator, set tooltip text for this forecast period.
     * 
     * @param   {number} aIndex   Index of forecast into forecasts array.
     */
    function initForecast(aIndex) {
        let tooltip = aCombinedData.forecasts.forecast[forecastIndices[aIndex]].abbreviatedSummary + ' - ' +
                      'Forecast issued: ' + aCombinedData.forecasts.date;
                      
        if ( widgetWidth > drawStartPosX ) {
            widgetWidth += 8;
            drawSeparator();
            CanWeatherWidget.mouseData.push({posX: widgetWidth, tooltip: tooltip});
            widgetWidth += 8;
        }
        else {
            // First item to be drawn, no separator.
            CanWeatherWidget.mouseData.push({posX: 0, tooltip: tooltip});
        }
    }
    
    /**
     * Draw forecast icon and or text to canvas.
     * 
     * @param   {object} aForecast   Forecast for an individual period.
     */
    function drawForecast(aForecast) {
        if ( aOptions.forecastInToolbar.showDayLabel ) {
            drawText(aForecast.periodName.slice(0, 3) + ':');
        }
    
        if ( aOptions.forecastInToolbar.showForecastIcon ) {
            if ( aOptions.forecastInToolbar.showDayLabel ) {
                widgetWidth += 4;
            }
            
            drawCondition(aForecast.iconCode,
                          aForecast.abbreviatedSummary.toLowerCase(),
                          'forecast');
        }
    
        var textString;
        
        if ( aOptions.forecastInToolbar.showForecastTemperature &&
           ( aForecast.high || aForecast.low ) ) {
            if ( aForecast.periodName == 'Today' ) {
                textString = aForecast.high;  
            }
            else if ( aForecast.periodName == 'Tonight' ) {
                textString = aForecast.low;
            }
            else {
                textString = ( aForecast.high || '--' ) + ' / ' + ( aForecast.low || '--' );
            }
        }
        
        if ( aOptions.forecastInToolbar.showPop &&
             aForecast.pop ) {
            textString += '  ' + aForecast.pop + '%';
        }
        
        if ( textString ) {
            widgetWidth += 4;
            drawText(textString);
        }
    }
    
    /**
     * Set display canvas prior to making visible.
     * 
     * @param   {object} aWorkingCanvas   Completed (text only) working canvas.
     * @param   {number} aWidth           Canvas width.
     */
    function setDisplayCanvas(aWorkingCanvas, aWidth) {
        // Clear old content, set new width, then copy.
        var canvas = CanWeatherWidget.canvasNodeList[CanWeatherWidget.hiddenCanvasIndex];
        var ctx = canvas.getContext('2d');
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        canvas.width= aWidth;
        ctx.drawImage(aWorkingCanvas, 0, 0);
        
        // If any images to be drawn, do them directly to the display canvas now.
        for ( var i = 0; i < CanWeatherWidget.imagesToDraw.length; i++ ) {
            var img = new Image();
            img.src = CanWeatherWidget.imagesToDraw[i][0];
            CanWeatherWidget.imagesToDraw[i][0] = img;
            
            // Preserve the loop value when inner function later executes.
            img.onload = (function(value){
                return function() {
                    ctx.drawImage(CanWeatherWidget.imagesToDraw[value][0],
                                  CanWeatherWidget.imagesToDraw[value][1],
                                  0);
                }
            })(i);
        }
    }
    
    /**
     * Double-buffering, show canvas just drawn to, hide the other.
     */
    function flipVisibility() {
        CanWeatherWidget.canvasNodeList[1-CanWeatherWidget.hiddenCanvasIndex].style.visibility = 'hidden';
        CanWeatherWidget.canvasNodeList[1-CanWeatherWidget.hiddenCanvasIndex].width = 0;
        CanWeatherWidget.canvasNodeList[CanWeatherWidget.hiddenCanvasIndex].style.visibility = 'visible';
        CanWeatherWidget.hiddenCanvasIndex = 1 - CanWeatherWidget.hiddenCanvasIndex;
    }
    
    /**
     * Emit widget width to add-on code.
     * 
     * @param   {number} aWidth   Widget width.
     * 
     * @returns {number}          Widget width.
     */
    function emitWidth(aWidth) {
        self.port.emit('width', aWidth);
    }
    
    /**
     * Draw condition images to canvas.
     * 
     * @param   {string} aIconCode           EC icon code.
     * @param   {string} aConditionSummary   Current/forecast summary.
     * @param   {string} aType               'current' or 'forecast'.
     */
    function drawCondition(aIconCode, aConditionSummary, aType) {
        switch ( aIconCode ) {
            case '00':
                // Sunny.
                // Fall through.
            
            case '01':
                // Mainly Sunny.
                saveImg(CanWeatherWidget.imgSun, 16);
                break;
    
            case '02':
                // Partly Cloudy.
                saveImg(CanWeatherWidget.imgSun, 16);
                saveImg(CanWeatherWidget.imgCloud, 23);
                break;
    
            case '03':
                // Mostly Cloudy.
                saveImg(CanWeatherWidget.imgCloud, 23);
                saveImg(CanWeatherWidget.imgSun, 16);
                break;
    
            case '34':
                // Night.
                // Fall through.
            
            case '04':
                // Increasing Cloud.
                saveImg(CanWeatherWidget.imgIncreasing, 13);
                saveImg(CanWeatherWidget.imgCloud, 23);
                break;
    
            case '35':
                // Night.
                // Fall through.
            
            case '05':
                // Decreasing Cloud.
                saveImg(CanWeatherWidget.imgDecreasing, 13);
                saveImg(CanWeatherWidget.imgCloud, 23);
                break;
    
            case '06':
                // Light Rainshower.
                saveImg(CanWeatherWidget.imgSun, 16);
                saveImg(CanWeatherWidget.imgRainDrop, 11);
                break;
    
            case '07':
                // Mixed Rain and Snow Shower.
                saveImg(CanWeatherWidget.imgRainDrop, 11);
                saveImg(CanWeatherWidget.imgSnowFlake, 18);
                break;
    
            case '08':
                // Light Snowshower.
                saveImg(CanWeatherWidget.imgSun, 16);
                saveImg(CanWeatherWidget.imgSnowFlake, 18);
                break;
    
            case '39':
                // Night.
                // Fall through.
            
            case '09':
                if ( aType == 'current' ) {
                    if ( aConditionSummary.indexOf('recent') != -1 ) {
                        if ( aConditionSummary.indexOf('rain') != -1 ) {
                            // Recent Thunderstorm with Rain.
                            saveImg(CanWeatherWidget.imgRecent, 18);
                            saveImg(CanWeatherWidget.imgLightning, 19);
                            saveImg(CanWeatherWidget.imgRainDrop, 11);
                        }
                        else {
                            // Recent Thunderstorm.
                            saveImg(CanWeatherWidget.imgRecent, 18);
                            saveImg(CanWeatherWidget.imgLightning, 19);
                        }
                    }
                    else {
                        // Lightning Visible; Thunderstorm without Precipitation.
                        saveImg(CanWeatherWidget.imgLightning, 19);
                    }
                }
                else {
                    if ( aConditionSummary.indexOf('thunder') != -1 ) {
                        saveImg(CanWeatherWidget.imgLightning, 19);
                        saveImg(CanWeatherWidget.imgRainDrop, 11);
                    }
                    else if ( aConditionSummary.indexOf('heavy') != -1 ) {
                        if ( aIconCode == '09' ) {
                            saveImg(CanWeatherWidget.imgSun, 16);
                        }
                        else {
                            saveImg(CanWeatherWidget.imgMoon, 17);
                        }
                        
                        saveImg(CanWeatherWidget.imgRainDrop, 11);
                        saveImg(CanWeatherWidget.imgRainDrop, 11);
                    }
                    else {
                        if ( aIconCode == '09' ) {
                            saveImg(CanWeatherWidget.imgSun, 16);
                        }
                        else {
                            saveImg(CanWeatherWidget.imgMoon, 17);
                        }
                        
                        saveImg(CanWeatherWidget.imgRainDrop, 11);
                    }
                    
                }
                
                break;
    
            case '10':
                // Cloudy.
                saveImg(CanWeatherWidget.imgCloud, 23);
                break;
    
            case '11':
                if ( aConditionSummary.indexOf('recent') != -1 ) {
                    // Recent Rainshower.
                    saveImg(CanWeatherWidget.imgRecent, 18);
                    saveImg(CanWeatherWidget.imgRainDrop, 11);
                }
                else if ( aConditionSummary.indexOf('distant') != -1 ) {
                    // Distant Precipitation.
                    saveImg(CanWeatherWidget.imgH2O, 22);
                }
                else {
                    // Squalls.
                    saveImg(CanWeatherWidget.imgWind, 16);
                    saveImg(CanWeatherWidget.imgWind, 16);
                    saveImg(CanWeatherWidget.imgRainDrop, 11);
                }
                
                break;
    
            case '12':
                if ( aType == 'current' ) {
                    if ( aConditionSummary.indexOf('recent') != -1 ) {
                        // Recent Rain.
                        saveImg(CanWeatherWidget.imgRecent, 18);
                        saveImg(CanWeatherWidget.imgRainDrop, 11);
                    }
                    else if ( aConditionSummary.indexOf('drizzle') != -1 ) {
                        // Mixed Rain and Drizzle.
                        saveImg(CanWeatherWidget.imgRainDrop, 11);
                        saveImg(CanWeatherWidget.imgDrizzle, 25);
                    }
                    else {
                        // Light Rain.
                        saveImg(CanWeatherWidget.imgRainDrop, 11);
                    }
                }
                else {
                    saveImg(CanWeatherWidget.imgRainDrop, 11);
                }
                
                break;
    
            case '13':
                if ( aType == 'current' ) {
                    if ( aConditionSummary.indexOf('heavy') != -1 ) {
                        if ( aConditionSummary.indexOf('drizzle') != -1 ) {
                            // Heavy Mixed Rain and Drizzle.
                            saveImg(CanWeatherWidget.imgRainDrop, 11);
                            saveImg(CanWeatherWidget.imgRainDrop, 11);
                            saveImg(CanWeatherWidget.imgDrizzle, 25);
                        }
                        else {
                            // Heavy Rain; Heavy Rainshower.
                            saveImg(CanWeatherWidget.imgRainDrop, 11);
                            saveImg(CanWeatherWidget.imgRainDrop, 11);
                            saveImg(CanWeatherWidget.imgRainDrop, 11);
                        }
                    }
                    else {
                        // Rain; Rainshower.
                        saveImg(CanWeatherWidget.imgRainDrop, 11);
                        saveImg(CanWeatherWidget.imgRainDrop, 11);
                    }
                }
                else {
                    saveImg(CanWeatherWidget.imgRainDrop, 11);
                }
                
                break;
    
            case '14':
                if ( aType == 'current' ) {
                    if ( aConditionSummary.indexOf('drizzle') != -1 ) {
                        // Recent Freezing Precipitation.
                        saveImg(CanWeatherWidget.imgRecent, 18);
                        saveImg(CanWeatherWidget.imgFreezing, 15);
                        saveImg(CanWeatherWidget.imgRainDrop, 11);
                    }
                    else if ( aConditionSummary.indexOf('drizzle') != -1 ) {
                        // Freezing drizzle; Light Freezing Drizzle.
                        saveImg(CanWeatherWidget.imgFreezing, 15);
                        saveImg(CanWeatherWidget.imgDrizzle, 25);
                    }
                    else {
                        // Freezing rain; Light Freezing Rain; Ice Pellets.
                        saveImg(CanWeatherWidget.imgFreezing, 15);
                        saveImg(CanWeatherWidget.imgRainDrop, 11);
                    }
                }
                else {
                    saveImg(CanWeatherWidget.imgFreezing, 15);
                    saveImg(CanWeatherWidget.imgRainDrop, 11);
                }
                
                break;
    
            case '15':
                if ( aType == 'current' ) {
                    if ( aConditionSummary.indexOf('recent') != -1 ) {
                        // Recent Rain and Snow.
                        saveImg(CanWeatherWidget.imgRecent, 18);
                        saveImg(CanWeatherWidget.imgRainDrop, 11);
                        saveImg(CanWeatherWidget.imgSnowFlake, 18);
                    }
                    else if ( aConditionSummary.indexOf('heavy') != -1 ) {
                        // Heavy Mixed Rain and Snow Shower; Heavy Rain and Snow.
                        saveImg(CanWeatherWidget.imgRainDrop, 11);
                        saveImg(CanWeatherWidget.imgRainDrop, 11);
                        saveImg(CanWeatherWidget.imgSnowFlake, 18);
                    }
                    else {
                        // Rain and Snow.
                        saveImg(CanWeatherWidget.imgRainDrop, 11);
                        saveImg(CanWeatherWidget.imgSnowFlake, 18);
                    }
                }
                else {
                    saveImg(CanWeatherWidget.imgRainDrop, 11);
                    saveImg(CanWeatherWidget.imgSnowFlake, 18);
                }
                
                break;
    
            case '16':
                if ( aType == 'current' ) {
                    if ( aConditionSummary.indexOf('recent') != -1 ) {
                        if ( aConditionSummary.indexOf('hail') != -1 ) {
                            // Recent Hail.
                            saveImg(CanWeatherWidget.imgRecent, 18);
                            saveImg(CanWeatherWidget.imgHail, 16);
                        }
                        else {
                            // Recent Snow; Recent Snowshower.
                            saveImg(CanWeatherWidget.imgRecent, 18);
                            saveImg(CanWeatherWidget.imgSnowFlake, 18);
                        }
                    }
                    else {
                        // Light Snow.
                        saveImg(CanWeatherWidget.imgSnowFlake, 18);
                    }
                }
                else {
                    saveImg(CanWeatherWidget.imgSnowFlake, 18);
                }
                
                break;
    
            case '17':
                if ( aType == 'current' ) {
                    // Snow; Heavy Snowshower.
                    saveImg(CanWeatherWidget.imgSnowFlake, 18);
                    saveImg(CanWeatherWidget.imgSnowFlake, 18);
                }
                else if ( aConditionSummary.indexOf('freezing') != -1 ) {
                    saveImg(CanWeatherWidget.imgSnowFlake, 18);
                    saveImg(CanWeatherWidget.imgFreezing, 15);
                    saveImg(CanWeatherWidget.imgRainDrop, 11);
                }
                else {
                    saveImg(CanWeatherWidget.imgSnowFlake, 18);
                    saveImg(CanWeatherWidget.imgSnowFlake, 18);
                }
                
                break;
    
            case '18':
                // Heavy Snow.
                saveImg(CanWeatherWidget.imgSnowFlake, 18);
                saveImg(CanWeatherWidget.imgSnowFlake, 18);
                saveImg(CanWeatherWidget.imgSnowFlake, 18);
                break;
    
            case '19':
                if ( aType == 'current' ) {
                    if ( aConditionSummary.indexOf('recent') != -1 ) {
                        if ( aConditionSummary.indexOf('hail') != -1 ) {
                            // Recent Thunderstorm with Hail; Recent Thunderstorm with Heavy Hail.
                            saveImg(CanWeatherWidget.imgRecent, 18);
                            saveImg(CanWeatherWidget.imgLightning, 19);
                            saveImg(CanWeatherWidget.imgHail, 16);
                        }
                        else {
                            // Recent Thunderstorm with Heavy Rain.
                            saveImg(CanWeatherWidget.imgRecent, 18);
                            saveImg(CanWeatherWidget.imgLightning, 19);
                            saveImg(CanWeatherWidget.imgRainDrop, 11);
                        }
                    }
                    else if ( aConditionSummary.indexOf('heavy') != -1 ) {
                        if ( aConditionSummary.indexOf('hail') != -1 ) {
                            // Heavy Thunderstorm with Hail.
                            saveImg(CanWeatherWidget.imgLightning, 19);
                            saveImg(CanWeatherWidget.imgLightning, 19);
                            saveImg(CanWeatherWidget.imgHail, 16);
                        }
                        else {
                            // Heavy Thunderstorm with Rain.
                            saveImg(CanWeatherWidget.imgLightning, 19);
                            saveImg(CanWeatherWidget.imgLightning, 19);
                            saveImg(CanWeatherWidget.imgRainDrop, 11);
                        }
                    }
                    else {
                        if ( aConditionSummary.indexOf('hail') != -1 ) {
                            // Thunderstorm with Hail.
                            saveImg(CanWeatherWidget.imgLightning, 19);
                            saveImg(CanWeatherWidget.imgHail, 16);
                        }
                        else {
                            // Thunderstorm with Rain.
                            saveImg(CanWeatherWidget.imgLightning, 19);
                            saveImg(CanWeatherWidget.imgRainDrop, 11);
                        }
                    }
                }
                else {
                    saveImg(CanWeatherWidget.imgLightning, 19);
                    saveImg(CanWeatherWidget.imgRainDrop, 11);
                }
                
                break;
    
            case '22':
                // Forecast only. A mix of sun and cloud.
                saveImg(CanWeatherWidget.imgCloud, 23);
                saveImg(CanWeatherWidget.imgSun, 16);
                break;
    
            case '23':
                // Current only. Haze.
                saveImg(CanWeatherWidget.imgHaze, 16);
                break;
    
            case '24':
                if ( aConditionSummary.indexOf('recent') != -1 ) {
                    // Recent Fog.
                    saveImg(CanWeatherWidget.imgRecent, 18);
                    saveImg(CanWeatherWidget.imgFog, 24);
                }
                else {
                    // Various Fog/Mist.
                    saveImg(CanWeatherWidget.imgFog, 24);
                }
                
                break;
    
            case '40':
                // Heavy Blowing Snow; Blowing Snow.
                // Fall through.
            
            case '25':
                if ( aConditionSummary.indexOf('heavy') != -1 ) {
                    // Heavy Drifting Snow.
                    saveImg(CanWeatherWidget.imgSnowFlake, 18);
                    saveImg(CanWeatherWidget.imgSnowFlake, 18);
                    saveImg(CanWeatherWidget.imgWind, 16);
                }
                else {
                    // Drifting Snow.
                    saveImg(CanWeatherWidget.imgSnowFlake, 18);
                    saveImg(CanWeatherWidget.imgWind, 16);
                }
                
                break;
    
            case '26':
                // Current only. Ice Crystals; Snow Crystals.
                saveImg(CanWeatherWidget.imgCrystal, 12);
                saveImg(CanWeatherWidget.imgCrystal, 12);
                break;
    
            case '27':
                if ( aType == 'current' ) {
                    if ( aConditionSummary.indexOf('heavy') != -1 ) {
                        // Heavy Hail; Heavy Snow Pellets.
                        saveImg(CanWeatherWidget.imgHail, 16);
                        saveImg(CanWeatherWidget.imgHail, 16);
                    }
                    else {
                        // Hail; Light Snow Pellets.
                        saveImg(CanWeatherWidget.imgHail, 16);
                    }
                }
                else {
                    saveImg(CanWeatherWidget.imgCrystal, 12);
                }
                
                break;
    
            case '28':
                if ( aType == 'current' ) {
                    if ( aConditionSummary.indexOf('recent') != -1 ) {
                        // Recent Drizzle.
                        saveImg(CanWeatherWidget.imgRecent, 18);
                        saveImg(CanWeatherWidget.imgDrizzle, 25);
                    }
                    else if ( aConditionSummary.indexOf('heavy') != -1 ) {
                        // Heavy Drizzle.
                        saveImg(CanWeatherWidget.imgDrizzle, 25);
                        saveImg(CanWeatherWidget.imgDrizzle, 25);
                    }
                    else {
                        // Light Drizzle; Drizzle; Snow Grains.
                        saveImg(CanWeatherWidget.imgDrizzle, 25);
                    }
                }
                else if ( aConditionSummary.indexOf('freezing') != -1 ) {
                    saveImg(CanWeatherWidget.imgFreezing, 15);
                    saveImg(CanWeatherWidget.imgDrizzle, 25);
                }
                else {
                    saveImg(CanWeatherWidget.imgDrizzle, 25);
                }
                
                break;
    
            case '99':
                // Default.
                // Fall through.
    
            case '29':
                // Not Reported.
                saveImg(CanWeatherWidget.imgFlag, 25);
                break;
    
            case '30':
                // Clear (night).
                // Fall through.
    
            case '31':
                // Mainly Clear (night).
                saveImg(CanWeatherWidget.imgMoon, 17);
                break;
    
            case '32':
                // Partly Cloudy (night).
                saveImg(CanWeatherWidget.imgMoon, 17);
                saveImg(CanWeatherWidget.imgCloud, 23);
                break;
    
            case '33':
                // Mostly Cloudy (night).
                saveImg(CanWeatherWidget.imgCloud, 23);
                saveImg(CanWeatherWidget.imgMoon, 17);
                break;
    
            case '36':
                // Light Rainshower.
                saveImg(CanWeatherWidget.imgMoon, 17);
                saveImg(CanWeatherWidget.imgRainDrop, 11);
                break;
    
            case '37':
                // Mixed Rain and Snow Shower.
                saveImg(CanWeatherWidget.imgMoon, 17);
                saveImg(CanWeatherWidget.imgRainDrop, 11);
                saveImg(CanWeatherWidget.imgSnowFlake, 18);
                break;
    
            case '38':
                // Light Snowshower.
                saveImg(CanWeatherWidget.imgMoon, 17);
                saveImg(CanWeatherWidget.imgSnowFlake, 18);
                break;
    
            case '41':
                // Funnel Cloud.
                saveImg(CanWeatherWidget.imgFunnel, 24);
                break;
    
            case '42':
                // Tornado.
                saveImg(CanWeatherWidget.imgTornado, 24);
                break;
    
            case '43':
                // Windy.
                    saveImg(CanWeatherWidget.imgWind, 16);
                    saveImg(CanWeatherWidget.imgWind, 16);
                break;
    
            case '44':
                // Smoke.
                saveImg(CanWeatherWidget.imgSmoke, 26);
                break;
    
            case '45':
                if ( aConditionSummary.indexOf('recent') != -1 ) {
                    // Recent Dust or Sand Storm.
                    saveImg(CanWeatherWidget.imgRecent, 18);
                    saveImg(CanWeatherWidget.imgDust, 21);
                }
                else if ( aConditionSummary.indexOf('thunderstorm') != -1 ) {
                    // Thunderstorm with Sand or Dust Storm.
                    saveImg(CanWeatherWidget.imgLightning, 19);
                    saveImg(CanWeatherWidget.imgDust, 21);
                }
                else {
                    // Dust; Dust Devils; Sand or Dust Storm; Severe Sand or Dust Storm.
                    saveImg(CanWeatherWidget.imgDust, 21);
                }
                
                break;
    
            default:
                // The flag icon.
                saveImg(CanWeatherWidget.imgFlag, 25);
                break;
        }
    }
});

/**
 * Listen for click events.
 * 
 * @param   {object}  aEvent          Click event object.
 * 
 * @returns {boolean} radarClicked    For left-click, was radar image clicked?
 */
this.addEventListener('click', function(aEvent) {
    if ( aEvent.button == 0 && aEvent.shiftKey == false ) {
        var radarClicked = ( CanWeatherWidget.radarEnabled && aEvent.clientX <= 25 ) ? true : false;
        self.port.emit('left-click', radarClicked);
    }
    else if ( aEvent.button == 2 || ( aEvent.button == 0 && aEvent.shiftKey == true ) ) {
        self.port.emit('right-click');
    }

    aEvent.preventDefault();
}, true);

/**
 * Listen for mouseover events.
 * 
 * @param   {object} aEvent    Mouseover event object.
 * 
 * @returns {string} tooltip   Tooltip text, assigned to widget tooltip.
 */
this.addEventListener('mouseover', function(aEvent) {
    var tooltip = '';
    
    for ( let i = 0; i < CanWeatherWidget.mouseData.length; i++ ) {
        if ( aEvent.clientX >= CanWeatherWidget.mouseData[i].posX ) {
            tooltip = CanWeatherWidget.mouseData[i].tooltip;
        }
    }
    
    self.port.emit('setTooltip', tooltip);
}, true);